unit IPollThread;
{$I LogOption.txt}

interface
uses
  {$IFDEF MSWINDOWS}
  Windows,
  {$ENDIF}
  SysUtils,
  DateUtils,
  IIOThread,
  IOPort,
  idata;

type
  EOIPollThread = class(EOIndustialIOThread)
  private
    FOwner: TObject;
    FPort: EOIOPort;

    FRawData: EOIOBuffer;

    FOutData: EOIOBuffer; // thread's outgoing data

    FOutDataLock: Integer;

    procedure AssemblePacket(Data: EOIOBuffer);

  protected
    procedure DoAssemble; virtual; abstract;

    function WriteToPort(Data: EOIOBuffer): Boolean;
    function ReadFromPort(Data: EOIOBuffer): Boolean;

    function Active: Boolean; virtual; abstract;
    procedure RemoveThread; virtual; abstract;

    procedure Execute; override;

  public
    property Owner: TObject read FOwner;
    property Port: EOIOPort read FPort;
    property RawData: EOIOBuffer read FRawData;

    property OutData: EOIOBuffer read FOutData;

    constructor Create(AOwner: TObject; IOPort: EOIOPort);
    destructor Destroy; override;

    procedure Reset;

    function LockOutData(Timeout: Integer): Boolean;
    procedure UnlockOutData;

  end;

implementation
uses
  AuditLog;

procedure EOIPollThread.AssemblePacket(Data: EOIOBuffer);
begin
  if Data.Count > 0 then
    FRawData.Push(Data);
  Data.Flush;

  DoAssemble; 
end;

function EOIPollThread.WriteToPort(Data: EOIOBuffer): Boolean;
begin
  try
    Result := FPort.Write(Data);
  except
    AuditLog.EventLog(AuditLog.etException, ClassName + '.WriteToPort', 'writing port: ' + FPort.PortName);
    raise;
  end;
end;

function EOIPollThread.ReadFromPort(Data: EOIOBuffer): Boolean;
begin
  try
    Result := FPort.Read(Data);
  except
    AuditLog.EventLog(AuditLog.etException, ClassName + '.ReadFromPort', 'reading port: ' + FPort.PortName);
    raise;
  end;
end;

procedure EOIPollThread.Execute;
var
  tmpBuffer: EOIOBuffer;
begin
  tmpBuffer := EOIOBuffer.Create;
  try
    while not Terminated do begin
      tmpBuffer.Flush;

      Sleep(1);
      if not ReadFromPort(tmpBuffer) then
        Terminate;

      AssemblePacket(tmpBuffer);

      Sleep(1);
      if LockOutData(50) then begin
        try
          tmpBuffer.Flush;
          tmpBuffer.Push(FOutData);
          FOutData.Flush;
          if not WriteToPort(tmpBuffer) then
            Terminate;
        finally
          UnlockOutData;
        end;
      end;

      if not Active then
        Terminate;
    end;

    RemoveThread;
  finally
    tmpBuffer.Free;
  end;
end;

constructor EOIPollThread.Create(AOwner: TObject; IOPort: EOIOPort);
begin
  Inherited Create;

  FOwner        := AOwner;
  FPort         := IOPort;

  FRawData      := EOIOBuffer.Create;

  FOutData      := EOIOBuffer.Create;

  FOutDataLock  := 0;
end;

destructor EOIPollThread.Destroy;
begin
  FOutData.Free;

  FRawData.Free;
  FPort.Free;

  Inherited;
end;

procedure EOIPollThread.Reset;
begin
  FRawData.Flush;
end;

function EOIPollThread.LockOutData(Timeout: Integer): Boolean;
var
  tmpI: Integer;
  startTime: TDateTime;
begin
  startTime := Now;
  repeat
    tmpI := InterlockedIncrement(FOutDataLock);
    Result := tmpI = 1;

    if not Result then
      InterlockedDecrement(FOutDataLock);

  until (Result) or (SecondsBetween(startTime, Now) > Timeout);
end;

procedure EOIPollThread.UnlockOutData;
begin
  if FOutDataLock > 0 then
    InterlockedDecrement(FOutDataLock);
end;

end.
